#include <Windows.h>
#include <gl/glu.h>
#include "CatAndMouse.h"

#define PATH_WIDTH 0.25f

GLfloat color_background[3] = {0,0,1};
GLfloat color_available_path[3] = {0.75f, 0.75f, 0};
GLfloat color_used_path[3] = {1, 1, 0};

void Piece::render_base() {
	glColor3fv(color_background);
	glRectf(0,0,1,1);
}

// Normal piece
class NormalPiece : public Piece  {
protected:
	PortMask original_ports_mask;
	PortMask available_ports_mask;
	int rotation; // For ease of implementation of render methods

protected:
	// I do not want NormalPiece to be created directly, only through its derivees.
	NormalPiece(PortMask shape) :
		 original_ports_mask(shape), available_ports_mask(shape), rotation(0) {}

public:
	virtual PortMask get_available_output_ports_mask(Port input_port) {
		if (available_ports_mask.contains(input_port))
			available_ports_mask = NONE;
		return original_ports_mask.remove(input_port);
	}

	virtual void rotate(int dir) {
		original_ports_mask = original_ports_mask.rotate(dir);
		available_ports_mask = available_ports_mask.rotate(dir);
		rotation += dir;
		while (rotation<0) rotation += 4;
		while (rotation>3) rotation -= 4;
	}

	virtual void reset_availability() {
		available_ports_mask = original_ports_mask;
	}
};

class AnglePiece : public NormalPiece {
public:
	AnglePiece() : NormalPiece(E|N) {}

	virtual void render() {
		render_base();
		GLUquadric *q = gluNewQuadric();
		glColor3fv(available_ports_mask.is_empty() ? color_used_path: color_available_path);
		glTranslatef(0.5f, 0.5f, 0);
		glRotatef(rotation*-90.f, 0.f, 0.f, 1.f);
		glTranslatef(-0.5f, -0.5f, 0);
		glTranslatef(1,1,0);
		gluPartialDisk(q, 0.5-PATH_WIDTH/2, 0.5+PATH_WIDTH/2, 32, 1, 180, 90);
		gluDeleteQuadric(q);
	}
};

class TeePiece : public NormalPiece {
public:
	TeePiece() : NormalPiece(E|N|W) {}

	virtual void render() {
		render_base();
		GLUquadric *q = gluNewQuadric();
		glColor3fv(available_ports_mask.is_empty() ? color_used_path: color_available_path);
		glTranslatef(0.5f, 0.5f, 0);
		glRotatef(rotation*-90.f, 0.f, 0.f, 1.f);
		glTranslatef(-0.5f, -0.5f, 0);
		glTranslatef(1,1,0);
		gluPartialDisk(q, 0.5-PATH_WIDTH/2, 0.5+PATH_WIDTH/2, 32, 1, 180, 90);
		glTranslatef(-1,0,0);
		gluPartialDisk(q, 0.5-PATH_WIDTH/2, 0.5+PATH_WIDTH/2, 32, 1, 90, 90);
		gluDeleteQuadric(q);
	}
};

class CrossPiece : public NormalPiece {
public:
	CrossPiece() : NormalPiece(E|N|W|S) {}

	virtual void render() {
		render_base();
		glColor3fv(available_ports_mask.is_empty() ? color_used_path: color_available_path);
		glRectf(0, 0.5-PATH_WIDTH/2, 1, 0.5+PATH_WIDTH/2);
		glRectf(0.5-PATH_WIDTH/2, 0, 0.5+PATH_WIDTH/2, 1);
	}
};

class BridgePiece : public Piece {
	int available_sections; // bit 0 = NS, bit 1 = EW
public:
	BridgePiece() : available_sections(0x03) {}

	virtual PortMask get_available_output_ports_mask(Port input_port) {
		int section = input_port==N || input_port==S ? 1 : 2;
		if (available_sections & section) {
			available_sections &= ~section;
			return PortMask(input_port).rotate(2);
		}
		return PortMask(NONE);
	}

	virtual void rotate(int dir) {
		if (dir & 1)
			available_sections = (available_sections >> 1) | ((available_sections & 1) << 1);
	}

	virtual void reset_availability() {
		available_sections = 0x03;
	}

	virtual void render() {
		render_base();
		glColor3fv(available_sections&2 ? color_available_path : color_used_path);
		glRectf(0, 0.5f-PATH_WIDTH/2, 1, 0.5f+PATH_WIDTH/2);
		glColor3fv(available_sections&1 ? color_available_path : color_used_path);
		glRectf(0.5f-PATH_WIDTH/2, 0, 0.5f+PATH_WIDTH/2, 0.5f-PATH_WIDTH/2-0.01f);
		glRectf(0.5f-PATH_WIDTH/2, 0.5f+PATH_WIDTH/2+0.01f, 0.5f+PATH_WIDTH/2, 1);
	}
};

class DoubleAnglePiece : public Piece {
	int available_sections; // bit 0 = first, bit 1 = second
	int rotation; // 0=first section is S-W, 1=first section is W-N
public:
	DoubleAnglePiece() : available_sections(0x03), rotation(0) {}

	virtual PortMask get_available_output_ports_mask(Port input_port) {
		PortMask section_port_mask(S | W);
		if (rotation == 1) section_port_mask = section_port_mask.rotate(1);
		int section_num = section_port_mask.contains(input_port) ? 1 : 2;
		if (available_sections & section_num) {
			available_sections &= ~section_num;
			if (section_num == 2) {
				section_port_mask = PortMask(N | E);
				if (rotation == 1) section_port_mask = section_port_mask.rotate(1);
			}
			return section_port_mask.remove(input_port);
		}
		return PortMask(NONE);
	}

	virtual void rotate(int dir) {
		while (dir<0) dir += 4;
		while (dir>3) dir -= 4;
		while (dir) {
			rotation ^= 1;
			if (rotation == 0)
				available_sections = (available_sections >> 1) | ((available_sections & 1) << 1);
			dir--;
		}
	}

	virtual void reset_availability() {
		available_sections = 0x03;
	}

	virtual void render() {
		render_base();
		GLUquadric *q = gluNewQuadric();
		glTranslatef(0.5f, 0.5f, 0);
		glRotatef(rotation*-90.f, 0.f, 0.f, 1.f);
		glTranslatef(-0.5f, -0.5f, 0);
		glTranslatef(1,1,0);
		glColor3fv(available_sections&2 ? color_available_path : color_used_path);
		gluPartialDisk(q, 0.5-PATH_WIDTH/2, 0.5+PATH_WIDTH/2, 32, 1, 180, 90);
		glTranslatef(-1,-1,0);
		glColor3fv(available_sections&1 ? color_available_path : color_used_path);
		gluPartialDisk(q, 0.5-PATH_WIDTH/2, 0.5+PATH_WIDTH/2, 32, 1, 0, 90);
		gluDeleteQuadric(q);
	}
};

Piece **Table::build_pieces() {
	Piece **p = new Piece*[9];
	p[0] = new BridgePiece();
	p[1] = new CrossPiece();
	p[2] = new TeePiece();
	p[3] = new TeePiece();
	p[4] = new AnglePiece();
	p[5] = new AnglePiece();
	p[6] = new AnglePiece();
	p[7] = new DoubleAnglePiece();
	p[8] = new DoubleAnglePiece();
	return p;
}

void Table::dispose_pieces(Piece **p) {
	for (int i=0; i<9; i++)
		delete p[i];
	delete []p;
}

Table::Table() {
	pieces = build_pieces();

	for (int i=0; i<3; i++)
		for (int j=0; j<3; j++)
			table[i][j] = pieces[i*3+j]; //NULL;
}

Table::~Table() {
	dispose_pieces(pieces);
}

void Table::render() {
	for (int i=0; i<3; i++) {
		for (int j=0; j<3; j++) {
			if (table[i][j]) {
				glPushMatrix();
				glTranslatef(1.f*i,1.f*j,0);
				table[i][j]->render();
				glPopMatrix();
			}
		}
	}
}